--
-- Author: mengjie0718@163.com
-- Date: 2014-12-23 11:21:41
--
require "lfs"
require "pack"
local updater = {}
updater.STATES = {
    kDownStart = "downloadStart",
    kDownDone = "downloadDone",
    kUncompressStart = "uncompressStart",
    kUncompressDone = "uncompressDone",
    unknown = "stateUnknown",
}

updater.ERRORS = {
    kCreateFile = "errorCreateFile",
    kNetwork = "errorNetwork",
    kNoNewVersion = "errorNoNewVersion",
    kUncompress = "errorUncompress",
    unknown = "errorUnknown";
}



local u  = nil
local f = cc.FileUtils:getInstance()
-- The res index file in original package.

local uroot = f:getWritablePath()
-- The directory for save updated files.
local ures = uroot.."cached_res/"

local size=32

local localResInfo = nil
local remoteResInfo = nil
local finalResInfo = nil

function updater.writeFile(path, content, mode)
    mode = mode or "w"
    local file = io.open(path, mode)
    if file then
        file:write(content)
        io.close(file)
        collectgarbage("collect")
        return true
    else
        return false
    end
end

function updater.readFile(path)
    return f:getFileData(path)
end

function updater.exists(path)
    return f:isFileExist(path)
end


function updater.mkdir(path)
    if not updater.exists(path) then
        return lfs.mkdir(path)
    end
    return true
end

function updater.rmdir(path)
    print("updater.rmdir:", path)
    if updater.exists(path) then
        local function _rmdir(path)
            local iter, dir_obj = lfs.dir(path)
            while true do
                local dir = iter(dir_obj)
                if dir == nil then break end
                if dir ~= "." and dir ~= ".." then
                    local curDir = path..dir
                    local mode = lfs.attributes(curDir, "mode") 
                    if mode == "directory" then
                        _rmdir(curDir.."/")
                    elseif mode == "file" then
                        os.remove(curDir)
                    end
                end
            end
            local succ, des = os.remove(path)
            if des then print(des) end
            return succ
        end
        _rmdir(path)
    end
    return true
end

-- Check local resinfo and remote resinfo, compare their version value.
function updater.checkUpdate(md5file)
    -- cc.PLATFORM_OS_IPHONE  = 4
    -- cc.PLATFORM_OS_IPAD    = 5
    if cc.Application:getInstance():getTargetPlatform() == 4 or cc.Application:getInstance():getTargetPlatform() == 5 then
        local l = string.pack("l", 0xFFFFFFFFFFFF)
        if #l >= 8 then
            size=64
        end
    end
    local function onRequestFinished(event)
        local ok = (event.name == "completed")
        local request = event.request
        if not ok then

            if request:getErrorCode()~=0 then
                print(request:getErrorCode())
                print(event.name )
                updater.checkUpdate(md5file)
            end
            return
        end

        local code = request:getResponseStatusCode()
        if code ~= 200 then
            updater.checkUpdate(md5file) 
        else
            local response = request:getResponseString()
            updater.readMd5File(response)
        end
    end
    local md5url = string.format("%s%s",updater.DOWNLOAD_URL,md5file)
    local request = cc.HTTPRequest:createWithUrl(onRequestFinished,md5url , cc.kCCHTTPRequestMethodGET)

    request:setTimeout(100)
    request:start()
    updater.handler("state","Check File")
end


function updater.readMd5File(md5Str)
    local remoteMd5Map =updater.md52table({},md5Str)
    local localMd5Map = updater.readLocalMd5File() 
    updater.md5ArrayDone = {}      
    updater.md5Array = {}          
    updater.totalSize = 0
    updater.downloadSize = 0
    for r_k, r_v in pairs(remoteMd5Map) do
        local l_v = localMd5Map[r_k]
        if l_v==nil or l_v=="" or l_v~=r_v then
            table.insert(updater.md5Array, r_k.."@"..r_v)
            updater.totalSize=updater.totalSize+tonumber(string.split(r_v, "_")[2])
        end
    end
    updater.maxsave = 0
    updater.errorCount = 0
    updater.downloadIndex = 0
    updater.md5Count      = #(updater.md5Array)
    updater.localMd5Map=localMd5Map
    if #updater.md5Array>0 then
        updater.handler(updater.STATES.kDownStart,"0/"..updater.md5Count)
    end
    updater.performWithDelayGlobal(function()
        updater.loadMd5File()
    end,1/30)
end
--复制本地文件到 data 目录
function updater.copyLocalFile()
    if #updater.localArray>0 then
        local md5Pair = string.split(table.remove(updater.localArray,1), "@")
        if #md5Pair<2 then
            updater.copyLocalFile()
        else
            if updater.saveFile(cc.HelperFunc:getFileData(updater.RES_ROOT_PATH..md5Pair[1]),md5Pair[1]) then
                updater.localMd5Map[md5Pair[1]]=md5Pair[2]
                updater.maxsave=updater.maxsave+1
                updater.downloadIndex = updater.downloadIndex+1
                updater.handler("progress",{updater.downloadIndex,updater.md5Count})
                if #updater.localArray % 20 ==0 then
                    updater.performWithDelayGlobal(function()
                        updater.copyLocalFile()
                    end,1/60)
                else
                    updater.copyLocalFile()
                end
            else
                print("saveError"..md5Pair[1])
                updater.localArray[#updater.localArray+1] = md5Pair[1].."@"..md5Pair[2]
                updater.performWithDelayGlobal(function()
                    updater.copyLocalFile()
                end,1/60) 
            end
        end
    else
        if updater.maxsave>10 then
            if updater.writeLocalMd5File() then
                updater.maxsave = 0
            end
        end
        updater.loadMd5File()
    end
end
function updater.loadMd5File()
    if updater.md5Count <=0 then 
        updater.handler("success","Enter Game")
    else
        -- print(cc.Network:getInternetConnectionStatus())
        local max = 5
        if updater.md5Count<max then
            max = updater.md5Count
        end
        for i=1,max do
            updater.performWithDelayGlobal(updater.downloadNextFile,1+i/60)
        end
    end
end

--read local md5
function updater.readLocalMd5File()
    updater.checkDirOK(DOWNLOAD_PATH)
    local localMd5Map = {}
    local localMd5Content = cc.HelperFunc:getFileData(DOWNLOAD_PATH.."md5.txt")
    if localMd5Content then
        updater.md52table(localMd5Map,localMd5Content)
    else
        local filePath = cc.FileUtils:getInstance():fullPathForFilename("md5.txt")
        localMd5Content = cc.HelperFunc:getFileData(filePath)
        if localMd5Content then
            updater.RES_ROOT_PATH=string.split(filePath, "md5.txt")[1]
            updater.md52table(localMd5Map,localMd5Content)
        end
    end
    return localMd5Map
end
function updater.md52table(table,md5Str)
    local md5Array =  string.split(md5Str, "\r")
    for k,v in ipairs(md5Array) do
        local value = v
        if value~=nil and value~="" then
            local md5Pair = string.split(value, "@")
            table[md5Pair[1]] =  md5Pair[2]
        end
    end
    return table
end


function updater.update(handler)
    updater.updateSecond=false
    updater.handler = handler
    updater.DOWNLOAD_URL = string.format("%s/%s/",RESOURCE[1],RESOURCE.xarg.resversion)
    updater.checkUpdate("md5.txt")
end
function updater.updateSecond(handler)
    updater.updateSecond=true
    updater.handler = handler
    updater.DOWNLOAD_URL = string.format("%s/%s/",RESOURCE[1],RESOURCE.xarg.resversion)
    updater.checkUpdate("md51.txt")
end
function updater.downloadFile(md5PairStr)
    local md5Pair    = string.split(md5PairStr, "@")
    if #md5Pair<2 then
        updater.downloadNextFile()
    else
        local fileUrl = string.format("%s%s",updater.DOWNLOAD_URL,md5Pair[1])
        if size==64 then 
            fileUrl=string.gsub(fileUrl,"script","script64")
        end
        local request = cc.HTTPRequest:createWithUrl(function(event) updater.downloadFileBack(event,md5PairStr) end , fileUrl, cc.kCCHTTPRequestMethodGET)
        request:setTimeout(120)
        request:start()
    end
end
function updater.downloadNextFile()
    if #updater.md5Array > 0  then
        local md5PairStr = table.remove(updater.md5Array,1)
        -- print("延时" )
        updater.performWithDelayGlobal(function()
            -- print("延时结束")
            updater.downloadFile(md5PairStr)
        end,1/60)
    elseif updater.downloadIndex>=updater.md5Count then
        if not updater.isEnd then
            updater.isEnd = true
            updater.performWithDelayGlobal(function()
                updater.handler("success","Enter Game")
            end,0.5)
        end
    end

end
--downloadFileBack
function updater.downloadFileBack(event,md5PairStr)
    if "failed" == event.name then
        print("failed" .. md5PairStr)
        updater.md5Array[#updater.md5Array+1] = md5PairStr
        updater.performWithDelayGlobal(function()
                updater.downloadNextFile()
        end,1/60) 
        event.request:cancel()
        return
    end
    local ok = (event.name == "completed")
    local request = event.request

    if not ok then
        if request:getErrorCode()~=0 then
            updater.md5Array[#updater.md5Array+1] = md5PairStr
            updater.performWithDelayGlobal(function()
                    updater.downloadNextFile()
            end,1/60) 
            request:cancel()
        end
        return
    end

    local code = tonumber(request:getResponseStatusCode())
    if code ~= 200 then
        request:cancel()
        if code>=400 then
            updater.downloadIndex = updater.downloadIndex+1
            updater.errorCount=updater.errorCount+1
            updater.downloadNextFile()
        else
            updater.md5Array[#updater.md5Array+1] = md5PairStr
            updater.performWithDelayGlobal(function()
                    updater.downloadNextFile()
            end,1/60) 
        end
        return
    end

    local md5Pair = string.split(md5PairStr, "@")
    if  updater.checkPath(md5Pair[1]) and request:saveResponseData(DOWNLOAD_PATH..md5Pair[1]) > 0 then
        updater.localMd5Map[md5Pair[1]]=md5Pair[2]
        updater.maxsave=updater.maxsave+1
        updater.downloadIndex = updater.downloadIndex+1
        updater.handler("progress",{updater.downloadIndex,updater.md5Count})
        if updater.maxsave>100 then
            if updater.writeLocalMd5File() then
                updater.maxsave = 0
            end
        end
        updater.downloadNextFile()
    else
        updater.md5Array[#updater.md5Array+1] = md5PairStr
        updater.performWithDelayGlobal(function()
                updater.downloadNextFile()
        end,1/60) 
    end
    request:cancel()
end

--write the done file md5s
function updater.writeLocalMd5File()
    local md5Str = {}
    for k,v in pairs(updater.localMd5Map) do
        md5Str[#md5Str+1]=k.."@"..v
    end
    return updater.writeFile(DOWNLOAD_PATH.."md5.txt", table.concat(md5Str,"\r"))
end
function updater.checkPath(filePath)
    if not f:isFileExist(fullPath) then
        local downloadPath = DOWNLOAD_PATH
        local dirArray = string.split(filePath, "/")
        local num = #dirArray
        for i=1,num-1 do --check the dir and create
            downloadPath = downloadPath..dirArray[i].."/"
            updater.checkDirOK(downloadPath)
        end  
    end
    return true    
end
function updater.saveFile(netFileData,filePath)

    -- local netFileData  = request:getResponseData()
    local downloadPath = DOWNLOAD_PATH
    local fullPath     = downloadPath..filePath
    if f:isFileExist(fullPath) then
        return updater.writeFile(fullPath, netFileData) 
    else
        --create new dir and download the file
        local dirArray = string.split(filePath, "/")
        local num = #dirArray
        for i=1,num-1 do --check the dir and create
            downloadPath = downloadPath..dirArray[i].."/"
            updater.checkDirOK(downloadPath)
        end
        return updater.writeFile(fullPath, netFileData)    
    end
    return false
end


function updater.updateFinalResInfo()
    if updater.maxsave>0 then
        if updater.writeLocalMd5File() then
            updater.maxsave = 0
        end
    end
    if updater.errorCount<=0 then
        local sharedUserDefault = cc.UserDefault:getInstance()
        local remoteResVersion = RESOURCE.xarg.resversion
        local localResVersion =  sharedUserDefault:getStringForKey("resversion")
        if remoteResVersion~=nil and remoteResVersion~="" and remoteResVersion~= localResVersion then
            sharedUserDefault:setStringForKey("resversion", remoteResVersion)
            sharedUserDefault:flush()
            APPResVersion=remoteResVersion
        end

        if updater.updateSecond then
            sharedUserDefault:setStringForKey("SecondRes", "2")
            sharedUserDefault:flush()
        end
    else
        print("error load file ")
    end
end

function updater.checkDirOK( path )

    local oldpath = lfs.currentdir()
    if lfs.chdir(path) then
        lfs.chdir(oldpath)
        return true
    end
    if lfs.mkdir(path) then
        return true
    end
    return false
end
function updater.clean()
    if u then
        u:unregisterScriptHandler()
        u:delete()
        u = nil
    end
    localResInfo = nil
    remoteResInfo = nil
    finalResInfo = nil
end

function string.split(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter=='') then return false end
    local pos,arr = 0, {}
    -- for each divider found
    for st,sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end
function string.trim(input)
    input = string.gsub(input, "^[ \t\n\r]+", "")
    return string.gsub(input, "[ \t\n\r]+$", "")
end
function string.ltrim(input)
    return string.gsub(input, "^[ \t\n\r]+", "")
end
return updater